﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Printing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using aoprinter.Model;
using aoprinter.IO;

namespace aoprinter.Draw
{
    class Drawer
    {
        private Graphics _g;
        private string _reportID;
        private Bitmap _canvas;
        private List<DrawPage> _drawPages;
        private ModPrinterSetting _setting;
        private int _nowPageIndex = 0;
        private int _nowImageIndex = 0;
        private SizeF _nowPageSize;
        private float _dpi = 96;

        public Drawer(string reportID, ModPrinterSetting setting)
        {
            if (setting == null)
            {
                throw new NullReferenceException("paint setting can't be null");
            }
            _reportID = reportID;
            _setting = setting;
            _drawPages = new List<DrawPage>();
            StartImage(SizeF.Empty);
        }

        ~Drawer()
        {
            EndImage();
            StyleCon.Instance.RemoveReport(_reportID);
        }

        private void StartImage(SizeF size)
        {
            _nowPageSize = size;
            EndImage();
            if (size.IsEmpty)
            {
                _canvas = new Bitmap(_setting.PageSetting.PaperSize.Width, _setting.PageSetting.PaperSize.Height);
            }
            else
            {
                _canvas = new Bitmap(Convert.ToInt32(ChangeMillimeterToPixel(size.Width)),
                    Convert.ToInt32(ChangeMillimeterToPixel(size.Height)));
            }
            MakeGraphic();
        }

        private void MakeGraphic()
        {
            _g = Graphics.FromImage(_canvas);
            _g.TextRenderingHint = TextRenderingHint.SystemDefault;
            _g.SmoothingMode = SmoothingMode.HighQuality;
            _g.CompositingQuality = CompositingQuality.HighQuality;
            _g.PageUnit = GraphicsUnit.Millimeter;
            _dpi = _g.DpiX;
        }

        private void EndImage()
        {
            if (_g != null)
            {
                _g.Dispose();
                _g = null;
            }
            if (_canvas != null)
            {
                _canvas.Dispose();
                _canvas = null;
            }
            GC.Collect();
        }

        private void ReSizeImage()
        {
            if (_g != null)
            {
                _g.Dispose();
                _g = null;
            }
            Bitmap oldCanvas = new Bitmap(_canvas);
            int width = _canvas.Width;
            int height = _canvas.Height + Convert.ToInt32(ChangeMillimeterToPixel(_nowPageSize.Height));
            EndImage();
            _canvas = new Bitmap(width, height);
            MakeGraphic();
            FillWhiteBackground(new RectangleF(0, 0, ChangePixelToMillimeter(_canvas.Width), ChangePixelToMillimeter(_canvas.Height)));
            _g.DrawImage(oldCanvas, PointF.Empty);
            oldCanvas.Dispose();
        }

        public void StartPage(int pageIndex, SizeF size)
        {
            var drawPage = _drawPages.Find(p => p.PageIndex == pageIndex);
            if (drawPage == null)
            {
                drawPage = new DrawPage(_reportID);
                drawPage.PageIndex = pageIndex;
                _drawPages.Add(drawPage);
                StartImage(size);
            }
            else
            {
                ChangeImge(pageIndex, 0);
            }
            _nowPageIndex = pageIndex;
            _nowImageIndex = 0;
        }

        public void EndPage(float headHeight, float footHeight)
        {
            var drawPage = _drawPages.Find(p => p.PageIndex == _nowPageIndex);
            if (drawPage != null)
            {
                drawPage.Canvas = new Bitmap(_canvas);
                drawPage.MakeImages(ChangeMillimeterToPixel(_nowPageSize.Height),
                    ChangeMillimeterToPixel(headHeight), ChangeMillimeterToPixel(footHeight));
                EndImage();
                _nowPageIndex = 0;
                _nowImageIndex = 0;
                return;
            }
            throw new NullReferenceException("can't find this page");
        }

        public void ChangeImge(int pageIndex, int imageIndex)
        {
            var drawPage = _drawPages.Find(p => p.PageIndex == pageIndex);
            if (drawPage != null)
            {
                if (imageIndex < drawPage.Images.Count)
                {
                    EndImage();
                    _canvas = drawPage.GetImage(imageIndex) as Bitmap;
                    MakeGraphic();
                    _nowPageIndex = pageIndex;
                    _nowImageIndex = imageIndex;
                    return;
                }
            }
            throw new IndexOutOfRangeException("can't find this image");
        }

        public void SaveImage()
        {
            var drawPage = _drawPages.Find(p => p.PageIndex == _nowPageIndex);
            if (drawPage != null)
            {
                if (_nowImageIndex < drawPage.Images.Count)
                {
                    drawPage.SaveImage(_canvas, _nowImageIndex);
                    EndImage();
                    _nowPageIndex = 0;
                    _nowImageIndex = 0;
                    return;
                }
            }
            throw new IndexOutOfRangeException("can't find this image");
        }

        public Image GetImage(int pageIndex, int imageIndex)
        {
            if (pageIndex < _drawPages.Count)
            {
                return _drawPages[pageIndex].GetImage(imageIndex);
            }
            return null;
        }

        public Image GetImage(int fullImageIndex)
        {
            if (fullImageIndex < TotalNumber)
            {
                for (int i = 0; i < _drawPages.Count; i++)
                {
                    for (int j = 0; j < _drawPages[i].ImageNum; j++)
                    {
                        if (fullImageIndex == 0)
                        {
                            return _drawPages[i].GetImage(j);
                        }
                        fullImageIndex--;
                    }
                }
            }
            return null;
        }

        public int PageNumber
        {
            get
            {
                return _drawPages.Count;
            }
        }

        public int ImageNumber(int pageIndex)
        {
            if (_drawPages != null)
            {
                var drawPage = _drawPages.Find(p => p.PageIndex == pageIndex);
                if (drawPage != null)
                {
                    return drawPage.ImageNum;
                }
            }
            return 0;
        }

        public int TotalNumber
        {
            get
            {
                return _drawPages.Sum(p => p.ImageNum);
            }
        }

        public int NowPageIndex
        {
            get
            {
                return _nowPageIndex;
            }
        }

        public int NowImageIndex
        {
            get
            {
                return _nowImageIndex;
            }
        }

        public void DrawText(string text, PointF location, ModTextStyle style)
        {
            if (string.IsNullOrEmpty(text))
            {
                return;
            }
            RectangleF rect = new RectangleF(location, GetTextSize(text, style));
            if (CanDraw(rect))
            {
                StyleCon.TextCon textCon = StyleCon.Instance.GetTextCon(_reportID, style);
                Font font = textCon.Font;
                Brush brush = textCon.Brush;
                _g.DrawString(text, font, brush, location);
            }
        }

        public void DrawLine(PointF point1, PointF point2, ModLineStyle style)
        {
            RectangleF rect = new RectangleF(Math.Min(point1.X, point2.X), Math.Min(point1.Y, point2.Y),
                Math.Abs(point1.X - point2.X), Math.Abs(point1.Y - point2.Y));
            if (CanDraw(rect))
            {
                Pen pen = StyleCon.Instance.GetPen(_reportID, style);
                ILineTypeOper lineOper = LineTypeFactory.GetLineOper(style.LineType);
                if (lineOper != null)
                {
                    GraphicsPath path = lineOper.DrawLine(point1, point2, style.Parameters);
                    if (path != null)
                    {
                        _g.DrawPath(pen, path);
                    }
                }
            }
        }

        public void FillRegion(RectangleF rect, ModRegionStyle style)
        {
            if (CanDraw(rect))
            {
                Brush brush = StyleCon.Instance.GetBrush(_reportID, style);
                _g.FillRegion(brush, new Region(rect));
            }
        }

        public void DrawImage(Image image, PointF point)
        {
            if (image == null)
            {
                return;
            }
            RectangleF rect = new RectangleF(point,
                new SizeF(ChangePixelToMillimeter(image.Size.Width), ChangePixelToMillimeter(image.Size.Height)));
            if (CanDraw(rect))
            {
                _g.DrawImage(image, point);
            }
        }

        public void DrawBackAndBorder(PointF location, SizeF size, ModRegionStyle background, ModLineStyle border)
        {
            if (background == null && border == null)
            {
                return;
            }
            RectangleF rect = new RectangleF(location, size);
            if (CanDraw(rect))
            {
                if (border != null)
                {
                    Pen pen = StyleCon.Instance.GetPen(_reportID, border);
                    ILineTypeOper lineOper = LineTypeFactory.GetLineOper(border.LineType);
                    GraphicsPath path = lineOper.DrawRect(rect, border.Parameters);
                    if (background != null)
                    {
                        Region region = new Region(path);
                        Brush brush = StyleCon.Instance.GetBrush(_reportID, background);
                        _g.FillRegion(brush, region);
                    }
                    if (path != null)
                    {
                        _g.DrawPath(pen, path);
                    }
                }
                if (background != null)
                {
                    Brush brush = StyleCon.Instance.GetBrush(_reportID, background);
                    _g.FillRectangle(brush, rect);
                }
            }
        }

        public void FillWhiteBackground(RectangleF rect)
        {
            if (_g != null)
            {
                Brush brush = StyleCon.Instance.GetBrush(_reportID, StyleCon.Instance.WhiteRegion);
                _g.FillRegion(brush, new Region(rect));
            }
        }

        public SizeF GetTextSize(string text, ModTextStyle style)
        {
            if (_g != null)
            {
                Font font = StyleCon.Instance.GetTextCon(_reportID, style).Font;
                return _g.MeasureString(text, font);
            }
            else
            {
                return SizeF.Empty;
            }
        }

        public SizeF GetLineSize(PointF point1, PointF point2, ModLineStyle style)
        {
            Pen pen = StyleCon.Instance.GetPen(_reportID, style);
            ILineTypeOper lineOper = LineTypeFactory.GetLineOper(style.LineType);
            if (lineOper != null)
            {
                GraphicsPath path = lineOper.DrawLine(point1, point2, style.Parameters);
                if (path != null)
                {
                    _g.DrawPath(pen, path);
                }
                return path.GetBounds(new Matrix(), pen).Size;
            }
            return SizeF.Empty;
        }

        private bool IfOutSize(RectangleF rect)
        {
            if (rect.Location.X < 0)
            {
                rect.Location = new PointF(0, rect.Location.Y);
            }
            if (rect.Location.Y < 0)
            {
                rect.Location = new PointF(rect.Location.X, 0);
            }
            RectangleF rectCanvas = new RectangleF(0, 0,
                ChangePixelToMillimeter(_canvas.Width),
                ChangePixelToMillimeter(_canvas.Height));
            return Convert.ToInt32((rectCanvas.Y + rectCanvas.Height)) < Convert.ToInt32(rect.Y + rect.Height);
        }

        private bool CanDraw(RectangleF rect)
        {
            if (_g == null)
            {
                return false;
            }
            if (IfOutSize(rect))
            {
                ReSizeImage();
                if (IfOutSize(rect))
                {
                    //if it isn't visible in next page too, then return
                    return false;
                }
            }
            return true;
        }

        public void ClearDrawPage()
        {
            if (Directory.Exists(_reportID))
            {
                try
                {
                    Directory.Delete(_reportID, true);
                }
                catch
                { }
            }
        }

        [System.Diagnostics.DebuggerStepThrough()]
        public float ChangeMillimeterToPixel(float millimeter)
        {
            return millimeter / (0.25399f * 100 / _dpi);
        }
        [System.Diagnostics.DebuggerStepThrough()]
        public float ChangePixelToMillimeter(float inch)
        {
            return inch * (0.25399f * 100 / _dpi);
        }
        [System.Diagnostics.DebuggerStepThrough()]
        private int TruncateInt(float value)
        {
            return Convert.ToInt32(Math.Round(value, MidpointRounding.ToEven));
        }

        class DrawPage
        {
            public DrawPage(string reportID)
            {
                Images = new List<string>();
                ReportID = reportID;
            }
            public string ReportID
            {
                set;
                get;
            }
            public int PageIndex
            {
                get;
                set;
            }
            public int ImageNum
            {
                get
                {
                    return Images.Count;
                }
            }
            public List<string> Images
            {
                get;
                private set;
            }
            public Bitmap Canvas
            {
                get;
                set;
            }

            public void MakeImages(float pageHeight, float headHeight, float footHeight)
            {
                //Canvas.Save(PageIndex.ToString() + ".jpg", ImageFormat.Jpeg);
                Images.Clear();
                int imageCount = Canvas.Height / Convert.ToInt32(pageHeight);
                int modHeight = Canvas.Height % Convert.ToInt32(pageHeight);
                if (modHeight > 0 && pageHeight < Canvas.Height)
                {
                    imageCount++;
                }
                for (int i = 0; i < imageCount; i++)
                {
                    Bitmap image = new Bitmap(Canvas.Width, TruncateInt(pageHeight + headHeight + footHeight));
                    Graphics g = Graphics.FromImage(image);
                    g.DrawImage(Canvas, 0, TruncateInt(headHeight),
                        new Rectangle(0, Canvas.Height * i / imageCount, Canvas.Width, TruncateInt(pageHeight)),
                        GraphicsUnit.Pixel);
                    g.Dispose();
                    if (!Directory.Exists(ReportID))
                    {
                        Directory.CreateDirectory(ReportID);
                    }
                    Images.Add(SaveImage(image, i));
                    image.Dispose();
                    image = null;
                }
                Canvas.Dispose();
                Canvas = null;
                GC.Collect();
            }

            public string SaveImage(Image image, int imageIndex)
            {
                if (image != null)
                {
                    string path = GetImagePath(imageIndex);
                    image.Save(path, ImageFormat.Jpeg);
                    return path;
                }
                return string.Empty;
            }

            public Image GetImage(int imageIndex)
            {
                if (imageIndex < Images.Count)
                {
                    string path = Images[imageIndex];
                    if (File.Exists(path))
                    {
                        Image image = Image.FromFile(path);
                        Image imageClone = new Bitmap(image);
                        image.Dispose();
                        image = null;
                        GC.Collect();
                        return imageClone;
                    }
                }
                return null;
            }

            [System.Diagnostics.DebuggerStepThrough()]
            private string GetImagePath(int imageIndex)
            {
                return ReportID + "/" + PageIndex.ToString() + "_" + imageIndex.ToString() + ".jpg";
            }

            [System.Diagnostics.DebuggerStepThrough()]
            private int TruncateInt(float value)
            {
                return Convert.ToInt32(Math.Round(value, MidpointRounding.ToEven));
            }
        }
    }
}